#pragma once
#include "../render/OpenGL/OpenGLBuffer.h"
#include "../render/Uniform.h"
POCELAIN_NAMESPACE_BEGIN
struct BufferAttributeRange
{
    int offset = 0;
    int count = -1;
};
//attribute 需要的条件
// name
// vao
// vbo
// ebo
// draw usage
// type
// itemSize
// normalized
class IBufferAttribute : public Ref
{
public:
    typedef IBufferAttribute type;
    //typedef std::vector<Real> array_t;
    IBufferAttribute() {}
    virtual ~IBufferAttribute() {}
    virtual size_t getItemSize()const{return mItemSize;}
    virtual const void *getDataArray()const=0;
    virtual size_t getDataArraySize()const=0;
    virtual size_t getDataArrayByteSize()const=0;
    virtual EUniformType getItemType()const =0 ;
    virtual EUniformType getAttributeType()const =0 ;
    bool needsUpdate = true;
    EOpenGLBufferUsagePattern getUsage()const{return mUsage;}
    void setUsage(EOpenGLBufferUsagePattern value )
    {
        mUsage = value;
    }
    size_t count()const{return mCount;}
    bool getNormalized()const{return mNormalized;}
    void setNeedsUpdate(bool val){ mNeedsUpdate = val;}
    bool getNeedsUpdate() const { return mNeedsUpdate;}
    // type &set(Real value,size_t offset = 0 )
    // {
    //     POCELAIN_ASSERT(mArray.size()>offset);
    // 	mArray[offset] = value;
    // 	return *this;
    // }
    // Real getX(size_t index )
    // {
    // 	return mArray[ index * mItemSize ];
    // }
    // type &setX(size_t index,Real x )
    // {
    // 	mArray[ index * mItemSize ] = x;
    // 	return *this;
    // }
    // Real getY(size_t index )
    // {
    // 	return mArray[ index * mItemSize + 1 ];
    // }
    // type &setY(size_t index,Real y )
    // {
    // 	mArray[ index * mItemSize + 1 ] = y;
    // 	return *this;
    // }
    // Real getZ(size_t index )
    // {
    // 	return mArray[ index * mItemSize + 2 ];
    // }
    // type &setZ(size_t index, Real z )
    // {
    // 	mArray[ index * mItemSize + 2 ] = z;
    // 	return *this;
    // }
    // Real getW(size_t index )
    // {
    // 	return mArray[ index * mItemSize + 3 ];
    // }
    // type &setW(size_t index,Real w )
    // {
    // 	mArray[ index * mItemSize + 3 ] = w;
    // 	return *this;
    // }
    // type &setXY(size_t index,Real x,Real y )
    // {
    // 	index *= mItemSize;
    // 	mArray[ index + 0 ] = x;
    // 	mArray[ index + 1 ] = y;
    // 	return *this;
    // }
    // type &setXYZ(size_t index,Real x,Real y,Real z )
    // {
    // 	index *= mItemSize;
    // 	mArray[ index + 0 ] = x;
    // 	mArray[ index + 1 ] = y;
    // 	mArray[ index + 2 ] = z;
    // 	return *this;
    // }
    // type &setXYZW(size_t index,Real x,Real y,Real z,Real w )
    // {
    // 	index *= mItemSize;
    // 	mArray[ index + 0 ] = x;
    // 	mArray[ index + 1 ] = y;
    // 	mArray[ index + 2 ] = z;
    // 	mArray[ index + 3 ] = w;
    // 	return *this;
    // }

    // type &onUpload( callback )
    // {
    // 	this->onUploadCallback = callback;
    // 	return *this;
    // }
protected:
    //array_t mArray;
    size_t mItemSize;
    size_t mCount = 0;
    bool mNormalized = false;
    bool mNeedsUpdate = true;
    EOpenGLBufferUsagePattern mUsage = EOpenGLBufferUsagePattern::StaticDraw;
    BufferAttributeRange mUpdateRange;
    //this.version = 0;
};

template<class T>
class TBufferAttribute : public IBufferAttribute
{
public:
    typedef TBufferAttribute<T> type;
    typedef std::vector<T> array_t;
    virtual EUniformType getItemType()const override
    {
        return GetUniformType<T>();
    }
    virtual EUniformType getAttributeType()const override
    {
        // TODO : bad design
        EUniformType type = GetUniformType<T>();
        switch(type)
        {
        case EUniformType::E_UNIFORM_BOOL:
            switch(mItemSize)
            {
            case 1:
                return type;
            case 2:
                return E_UNIFORM_BVECTOR2;
            case 3:
                return E_UNIFORM_BVECTOR3;
            case 4:
                return E_UNIFORM_BVECTOR4;
            }
            break;
//        case EUniformType::E_UNIFORM_BYTE:
//            break;
//        case EUniformType::E_UNIFORM_UBYTE:
//            break;
//        case EUniformType::E_UNIFORM_SHORT:
//            break;
//        case EUniformType::E_UNIFORM_USHORT:
//            break;
        case EUniformType::E_UNIFORM_INT:
            switch(mItemSize)
            {
            case 1:
                return type;
            case 2:
                return E_UNIFORM_IVECTOR2;
            case 3:
                return E_UNIFORM_IVECTOR3;
            case 4:
                return E_UNIFORM_IVECTOR4;
            }
            break;
        case EUniformType::E_UNIFORM_UINT:
            switch(mItemSize)
            {
            case 1:
                return type;
            case 2:
                return E_UNIFORM_UVECTOR2;
            case 3:
                return E_UNIFORM_UVECTOR3;
            case 4:
                return E_UNIFORM_UVECTOR4;
            }
            break;
        case EUniformType::E_UNIFORM_FLOAT:
            switch(mItemSize)
            {
            case 1:
                return type;
            case 2:
                return E_UNIFORM_VECTOR2;
            case 3:
                return E_UNIFORM_VECTOR3;
            case 4:
                return E_UNIFORM_VECTOR4;
            }
            break;
        case EUniformType::E_UNIFORM_DOUBLE:
            switch(mItemSize)
            {
            case 1:
                return type;
            case 2:
                return E_UNIFORM_DVECTOR2;
            case 3:
                return E_UNIFORM_DVECTOR3;
            case 4:
                return E_UNIFORM_DVECTOR4;
            }
            break;
        }
        abort();
    }
    TBufferAttribute(const array_t &array,size_t itemSize,bool normalized = false)
    {
        mArray = array;
        mItemSize = itemSize;
        mCount = array.size() / itemSize;
        mNormalized = normalized == true;
    }
    virtual ~TBufferAttribute()
    {
    }
    const void *getDataArray()const override
    {
        return mArray.data();
    }
    virtual size_t getDataArraySize()const override
    {
        return mArray.size();
    }
    virtual size_t getDataArrayByteSize()const override
    {
        return mArray.size()*sizeof(T);
    }
    type &applyMatrix3(const TMatrix<T,3,3> &m )
    {
        Vector3 _vector;
        Vector2 _vector2;
        if ( mItemSize == 2 )
        {
            for ( size_t i = 0; i < mCount; ++i )
            {
                _vector2.set(
                             getX(i),
                             getY(i)
                             );

                _vector2.applyMatrix3( m );

                setXY( i, _vector2.x, _vector2.y );
            }
        }
        else if ( mItemSize == 3 )
        {
            for ( size_t i = 0; i < mCount;  ++i )
            {
                _vector.set(
                            getX(i),
                            getY(i),
                            getZ(i)
                            );
                _vector.applyMatrix3( m );
                setXYZ( i, _vector.x, _vector.y, _vector.z );
            }
        }
        return *this;
    }

    type &applyMatrix4(const TMatrix<T,4,4> &m )
    {
        Vector3 _vector;
        for ( size_t i = 0; i < mCount; ++i )
        {
            _vector.x = getX( i );
            _vector.y = getY( i );
            _vector.z = getZ( i );
            _vector.applyMatrix4( m );
            setXYZ( i, _vector.x, _vector.y, _vector.z );
        }
        return *this;
    }

    type &applyNormalMatrix(const TMatrix<T,3,3> &m )
    {
        Vector3 _vector;
        for ( size_t i = 0; i < mCount; ++i )
        {
            _vector.set(
                        getX( i ),
                        getY( i ),
                        getZ( i )
                        );
            _vector.applyNormalMatrix( m );
            setXYZ( i, _vector.x, _vector.y, _vector.z );
        }
        return *this;
    }

    type &transformDirection(const TMatrix<T,4,4> &m)
    {
        Vector3 _vector;
        for ( size_t i = 0; i < mCount; ++i )
        {
            _vector.set(
                        getX( i ),
                        getY( i ),
                        getZ( i )
                        );
            _vector.transformDirection( m );
            setXYZ( i, _vector.x, _vector.y, _vector.z );
        }
        return *this;
    }
    type &set(T value,size_t offset = 0 )
    {
        POCELAIN_ASSERT(mArray.size()>offset);
        mArray[offset] = value;
        return *this;
    }
    T getX(size_t index ) const
    {
        return mArray[ index * mItemSize ];
    }
    type &setX(size_t index,T x )
    {
        mArray[ index * mItemSize ] = x;
        return *this;
    }
    T getY(size_t index ) const
    {
        return mArray[ index * mItemSize + 1 ];
    }
    type &setY(size_t index,T y )
    {
        mArray[ index * mItemSize + 1 ] = y;
        return *this;
    }
    T getZ(size_t index ) const
    {
        return mArray[ index * mItemSize + 2 ];
    }
    type &setZ(size_t index, T z )
    {
        mArray[ index * mItemSize + 2 ] = z;
        return *this;
    }
    T getW(size_t index ) const
    {
        return mArray[ index * mItemSize + 3 ];
    }
    type &setW(size_t index,T w )
    {
        mArray[ index * mItemSize + 3 ] = w;
        return *this;
    }
    type &setXY(size_t index,T x,T y )
    {
        index *= mItemSize;
        mArray[ index + 0 ] = x;
        mArray[ index + 1 ] = y;
        return *this;
    }
    type &setXYZ(size_t index,T x,T y,T z )
    {
        index *= mItemSize;
        mArray[ index + 0 ] = x;
        mArray[ index + 1 ] = y;
        mArray[ index + 2 ] = z;
        return *this;
    }
    type &setXYZW(size_t index,T x,T y,T z,T w )
    {
        index *= mItemSize;
        mArray[ index + 0 ] = x;
        mArray[ index + 1 ] = y;
        mArray[ index + 2 ] = z;
        mArray[ index + 3 ] = w;
        return *this;
    }
    TBox<T,3> getBoundingBox() const
    {
        TBox<T,3> ret;
        T minX = TBox<T,3>::limit_t::max();
        T minY = TBox<T,3>::limit_t::max();
        T minZ = TBox<T,3>::limit_t::max();

        T maxX = TBox<T,3>::limit_t::min();
        T maxY = TBox<T,3>::limit_t::min();
        T maxZ = TBox<T,3>::limit_t::min();

        for ( size_t i = 0; i < mCount; ++i )
        {
            const auto x = getX( i );
            const auto y = getY( i );
            const auto z = getZ( i );

            if ( x < minX ) minX = x;
            if ( y < minY ) minY = y;
            if ( z < minZ ) minZ = z;

            if ( x > maxX ) maxX = x;
            if ( y > maxY ) maxY = y;
            if ( z > maxZ ) maxZ = z;

        }

        ret.min.set( minX, minY, minZ );
        ret.max.set( maxX, maxY, maxZ );

        return ret;
    }
    TBox<T,3> getBoundingSphere() const
    {
        TBox<T,3> ret;
        T minX = TBox<T,3>::limit_t::max();
        T minY = TBox<T,3>::limit_t::max();
        T minZ = TBox<T,3>::limit_t::max();

        T maxX = TBox<T,3>::limit_t::min();
        T maxY = TBox<T,3>::limit_t::min();
        T maxZ = TBox<T,3>::limit_t::min();

        for ( size_t i = 0; i < mCount; ++i )
        {
            const auto x = getX( i );
            const auto y = getY( i );
            const auto z = getZ( i );

            if ( x < minX ) minX = x;
            if ( y < minY ) minY = y;
            if ( z < minZ ) minZ = z;

            if ( x > maxX ) maxX = x;
            if ( y > maxY ) maxY = y;
            if ( z > maxZ ) maxZ = z;

        }

        ret.min.set( minX, minY, minZ );
        ret.max.set( maxX, maxY, maxZ );

        return ret;
    }
    TVector<T,2> toVector2(size_t index) const
    {
        return TVector<T,2>(
                            getX(index),
                            getY(index)
                            );
    }
    TVector<T,3> toVector3(size_t index) const
    {
        return TVector<T,3>(
                            getX(index),
                            getY(index),
                            getZ(index)
                            );
    }
    TVector<T,4> toVector4(size_t index) const
    {
        return TVector<T,4>(
                            getX(index),
                            getY(index),
                            getZ(index),
                            getW(index)
                            );
    }
protected:
    array_t mArray;
};
typedef TBufferAttribute<uint32_t> UInt32BufferAttribute;
typedef TBufferAttribute<float> Float32BufferAttribute;

POCELAIN_NAMESPACE_END
